package sk.fiit.vppj.eventmanager.core;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.RowMapper;

import sk.fiit.vppj.eventmanager.data.BasicUser;

/**
 * Class provides application with logged in user.
 * 
 * @author Marek
 * 
 */
public final class UserManager {
	private final static UserManager userManager = new UserManager();
	private BasicUser user = null;
	private final static Logger log = Logger.getLogger( UserManager.class.getName() );

	private UserManager() {
	}

	public static UserManager getInstance() {
		return userManager;
	}

	/**
	 * Method tries to log in user according to his credentials
	 * @param username for getting into application
	 * @param password for getting into application
	 */
	public void login(String username, String password) {

		//checks whether password or username are not empty
		if( username.isEmpty() || password.isEmpty() ) {
			ErrorHandler.getInstance().showErrorMessage( "You've not fill username or password field!" );
			log.log( Level.INFO, "Password or username was not filled" );
			throw new IllegalArgumentException( "Password or username was not filled!" );
		}
		try {

			String[] userPasswordSplitted = splitPassword( username );
			if( userPasswordSplitted[0].equals( generatePassword( password, userPasswordSplitted[1] ) ) ) {

				this.user = getUserFromDB( username );
			}
			else {
				log.log( Level.INFO, "Password or username was incorrect" );
				ErrorHandler.getInstance().showErrorMessage( "Username or password are incorrect!" );
				return;
			}
		}
		catch ( DataAccessException e ) {
			log.log( Level.SEVERE, "Connection to database failed", e );
			ErrorHandler.getInstance().showErrorMessage( "Username or password are incorrect!" );
			e.printStackTrace();
		}

	}

	/**
	 * Takes user password from database and divide salt from hashed password
	 * @param username username of user, who wants to log in
	 * @return splitted salt and hashed password
	 */
	private String[] splitPassword(String username) {
		String sql = "SELECT password FROM jos_users WHERE username = ?";
		String userPassword = ( String ) DatabaseManager.getInstance().getJdbcTemplate()
				.queryForObject( sql, new String[] { username }, java.lang.String.class );

		if( userPassword.isEmpty() ) {
			ErrorHandler.getInstance().showErrorMessage( "Username or password are incorrect!" );
			log.log( Level.INFO, "Password or username was incorrect" );
			throw new IllegalStateException( "Username or password are incorrect!" );
		}

		// spliting gained password into password part [0] and salt part [1]
		return userPassword.split( ":" );
	}

	/**
	 * Method takes basic information about user from database and maps it into BasicUser object
	 * @param username
	 * @return instance of BasicUser object
	 */
	private BasicUser getUserFromDB(String username) {
		String sql = "SELECT id, name, usertype, class, event_id FROM jos_users, jos_event_manager_users WHERE username = ? AND id = user_id";

		return ( BasicUser ) DatabaseManager.getInstance().getJdbcTemplate()
				.queryForObject( sql, new String[] { username }, new RowMapper<Object>() {

					public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
						BasicUser tempUser = new BasicUser();
						tempUser.setId( rs.getInt( "id" ) );
						tempUser.setName( rs.getString( "name" ) );
						tempUser.setUsertype( rs.getString( "usertype" ) );
						tempUser.setClazz( rs.getString( "class" ) );
						tempUser.setEvent_id( rs.getInt( "event_id" ) );

						return tempUser;
					}

				} );
	}

	/**
	 * Generates hashed password from salt and input
	 * @param input
	 * @param salt
	 * @return Hash which compares with password from database
	 */
	private String generatePassword(String input, String salt) {

		byte[] hash = getMD5hash( input, salt );
		if( hash == null ) {
			throw new NullPointerException( "Password hash is null!" );
		}
		return new BigInteger( 1, hash ).toString( 16 );

	}

	private byte[] getMD5hash(String input, String salt) {
		byte[] hash = null;
		try {
			input = input.concat( salt );
			MessageDigest algorithm = MessageDigest.getInstance( "MD5" );
			algorithm.update( input.getBytes() );
			hash = algorithm.digest();
		}
		catch ( NoSuchAlgorithmException e ) {
			log.log( Level.WARNING, "Crypting algorithm is not installed!", e );
			e.printStackTrace();
		}
		return hash;
	}

	public BasicUser getUser() {
		return this.user;
	}

	public void logout() {
		this.user = null;
	}

	public boolean isLogIn() {

		if( this.user == null ) {
			return false;
		}
		else {
			return true;
		}

	}

}
